package com.advert.presentation.bean.advert;

import com.advert.config.Config;
import com.advert.config.ImageConfig;
import com.advert.dto.image.ImageScalingResult;
import com.advert.dto.image.ScalingDimensions;
import com.advert.exception.ApplicationRuntimeException;
import com.advert.model.advert.AdvertEntity;
import com.advert.model.security.UserInfoEntity;
import com.advert.model.support.AdvertType;
import com.advert.model.advert.ImageEntity;
import com.advert.model.vehicle.VehicleEntity;
import com.advert.presentation.tools.FacesHelper;
import com.advert.presentation.util.ActionOutcome;
import com.advert.security.AppSecurityManager;
import com.advert.service.advert.AdvertService;
import com.advert.service.security.SecurityService;
import com.advert.service.util.ImageProcessingService;
import com.advert.util.Event;
import com.advert.presentation.util.localization.LabelsMap;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.*;
import org.jboss.seam.core.Events;
import org.jboss.seam.log.Log;
import org.richfaces.event.UploadEvent;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;


@Name("sellAdvertBean")
@Scope(ScopeType.PAGE)
public class SellAdvertBean implements Serializable {
    @Logger
    private Log logger;

    @In("AdvertService")
    private AdvertService advertService;
    @In("ImageProcessingService")
    private ImageProcessingService imageProcessingService;
    @In("SecurityManager")
    private AppSecurityManager securityManager;
    @In("SecurityService")
    private SecurityService securityService;
    @In("facesHelper")
    private FacesHelper facesHelper;
    @In("labels")
    private LabelsMap labels;
    @In("uploadedImagesDirectory")
    private File uploadedImagesDirectory;
    @In("config")
    private Config config;

    private AdvertEntity advert;
    private Integer advertId;
    private List<Future<ImageScalingResult>> imageScalingTasks;

    private boolean addPhoto;
    private boolean agreementPrice;
    private boolean customBrand;
    private Map<Integer, Boolean> imageControl;


    // Accessors

    public AdvertEntity getAdvert() {
        if (advert == null) {
            initAdvert();
        }
        return advert;
    }

    public boolean isAddPhoto() {
        return addPhoto;
    }

    public void setAddPhoto(boolean addPhoto) {
        this.addPhoto = addPhoto;
    }

    public boolean isAgreementPrice() {
        return agreementPrice;
    }

    public void setAgreementPrice(boolean agreementPrice) {
        this.agreementPrice = agreementPrice;
    }

    public Integer getAdvertId() {
        return advertId;
    }

    public void setAdvertId(Integer advertId) {
        this.advertId = advertId;
    }

    public Map<Integer, Boolean> getImageControl() {
        return imageControl;
    }

    public boolean isCustomBrand() {
        return customBrand;
    }

    public void setCustomBrand(boolean customBrand) {
        this.customBrand = customBrand;
    }
    
    // Actions

    public AdvertEntity createAdvert() {
        AdvertEntity retVal = null;
        try {
            prepareAdvert(advert);
            List<ImageScalingResult> results = getScalingResults(config.getPhotoQuantity());
            retVal = advertService.createAdvert(advert, results, addPhoto);
        } catch (IOException e) {
            logger.error("Error operating with uploaded image files", e);
            throw new ApplicationRuntimeException("Error operating with uploaded image files", e.getCause());
        }
        facesHelper.addInfo(labels.get("ADVERT_ADDED_SUCCESSFULLY_MESSAGE"), true);
        return retVal;
    }

    public AdvertEntity updateAdvert() {
        facesHelper.addInfo(labels.get("ADVERT_UPDATED_SUCCESSFULLY_MESSAGE"), true);
        prepareAdvert(advert);
        return advertService.updateAdvert(advert, imageControl);
    }

    public Boolean saveImages() {
        try {
            int canSave = config.getPhotoQuantity() - getAdvert().getImages().size();
            List<ImageScalingResult> scalingResults;
            scalingResults = getScalingResults(canSave);
            for (ImageScalingResult scalingResult : scalingResults) {
                imageProcessingService.createImage(scalingResult, getAdvert(), uploadedImagesDirectory);  
            }

            facesHelper.addInfo(labels.get("IMAGES_ADDED_SUCCESSFULLY_MESSAGE"), true);
            return true;
        } catch (IOException e) {
            logger.error("Error operating with uploaded image files", e);
            throw new ApplicationRuntimeException("Error operating with uploaded image files", e.getCause());
        }
    }

    public String cancel() {
        getScalingResults(0);
        return ActionOutcome.CANCEL.name();
    }

    public String beforeAddPhotoCheck() {
        String retVal = ActionOutcome.SUCCESS.name();
        if (getAdvert().getImages().size() >= config.getPhotoQuantity()) {
            facesHelper.addError(labels.get("MAX_PHOTO_QUANTITY_EXCEED", config.getPhotoQuantity()), true);
            return ActionOutcome.FAILED.name();
        }
        return retVal;
    }


    // Listeners

    /**
     *  Process file upload event by starting image scaling task
     * @param event
     */
    public void onFileUploaded(UploadEvent event) {
        ScalingDimensions dimensions = new ScalingDimensions(imageConfig().getWidth(), imageConfig().getHeight(), imageConfig().getPreviewWidth(), imageConfig().getPreviewHeight());
        File uploadedFile = event.getUploadItem().getFile();
        Future<ImageScalingResult> future = imageProcessingService.scaleImage(uploadedFile, dimensions);
        imageScalingTasks.add(future); 
    }

    public void typeChanged() {
        advert.getVehicle().setCategoryCode(null);
        categoryChanged();
    }

    public void categoryChanged() {
        advert.getVehicle().setBrandId(null);
        brandNameChanged();
    }

    public void brandNameChanged() {
        Events.instance().raiseEvent(Event.VEHICLE_MODEL_REQUIRED, advert.getVehicle());

        advert.getVehicle().setModelId(null);
        modelNameChanged();
    }

    public void modelNameChanged() {
        advert.getVehicle().setReleaseYear(null);
    }

    
    @Create
    public void init() {
        imageScalingTasks = new ArrayList<Future<ImageScalingResult>>();
        addPhoto = false;
        agreementPrice = false;
    }

    private void initAdvert() {
        if (advertId != null) {
            advert = advertService.getAdvert(advertId);
            if (advert != null) {
                if (advert.getPrice() == null) {
                    agreementPrice = true;
                }
                if (advert.getVehicle().getBrandId() == null) {
                    customBrand = true;
                }

                imageControl = new HashMap<Integer, Boolean>();
                for (ImageEntity image : advert.getImages()) {
                    imageControl.put(image.getId(), false);
                }

                Events.instance().raiseEvent(Event.VEHICLE_MODEL_REQUIRED, advert.getVehicle());
            }
        } else {
            advert = new AdvertEntity();
            advert.setType(AdvertType.S);
            advert.setVehicle(new VehicleEntity());

            UserInfoEntity userInfo = securityService.getUserInfo(securityManager.userId());
            advert.setContactInfo(userInfo.getContactInfo());
        }
    }

    private void prepareAdvert(AdvertEntity advert) {
        if (agreementPrice) {
            advert.setPrice(null);
            advert.setCurrencyCd(null);
        }
        if (advert.getAdvertDate() == null) {
            advert.setAdvertDate(new Date());
        }
        if (customBrand) {
            advert.getVehicle().setBrandId(null);
            advert.getVehicle().setModelId(null);
        } else {
            advert.getVehicle().setBrandName(null);
            advert.getVehicle().setModelName(null);
        }
    }

    /**
     * Trims image list to permitted size.
     * Images that is not got in list will be deleted.
     *
     * @param results - list of processing results
     * @param maxResultsQuantity - max quantity that should be returned, rest will be cleaned up
     * @return
     */
    private List<ImageScalingResult> trimImageList(List<ImageScalingResult> results, int maxResultsQuantity) {
        List<ImageScalingResult> retVal = results;
        if (maxResultsQuantity < 0) {
            maxResultsQuantity = 0;
        }
        if (results.size() > maxResultsQuantity) {
            retVal = results.subList(results.size() - maxResultsQuantity, results.size());
            for (ImageScalingResult result : results.subList(0, results.size() - maxResultsQuantity)) {
                imageProcessingService.removeImages(result);
            }
        }
        return retVal;
    }

    /**
     * Waits to image scaling tasks finished, then returns scaling results
     *
     * @param maxResultsQuantity - max quantity that should be returned
     * @return
     */
    private List<ImageScalingResult> getScalingResults(int maxResultsQuantity) {
        try {
            List<ImageScalingResult> results = new ArrayList<ImageScalingResult>();
            for (Future<ImageScalingResult> imageScalingTask : imageScalingTasks) {
                results.add(imageScalingTask.get());
            }
            return trimImageList(results, maxResultsQuantity);

        } catch (InterruptedException e) {
            logger.fatal("Image scaling future task interrupted", e);
            throw new ApplicationRuntimeException("Image scaling future task interrupted", e.getCause());
        } catch (ExecutionException e) {
            logger.error("Image scaling task threw exception", e);
            throw new ApplicationRuntimeException("Image scaling task threw exception", e.getCause());
        }
    }

    private ImageConfig imageConfig() {
        return config.getImageConfig();
    }
}
